import Stack from '../js-data-structure/Stack.js'

const OPERATION = {
    '+': 1,
    '-': 1,
    '*': 2,
    '/': 2,
}

/**
 * 支持加减乘除 小数 可以含空格
 * @param expression
 * @return {*}
 */
function doCalc(expression){
    let infixList = toInfixExpressionList(expression)
    let suffixList = parseSuffixExpressionList(infixList)
    let result = calculate(suffixList)
    return result
}

// 中缀表达式转后缀表达式 式 1 + ( ( 2 + 3 )× 4) - 5  => 1 2 3 + 4 * + 5 -
// 1 初始化两个栈：运算符栈s1和储存中间结果的栈s2；
// 2 从左至右扫描中缀表达式；
// 3 遇到操作数时，将其压s2；
// 4 遇到运算符时，比较其与s1栈顶运算符的优先级：
//      4.1 如果s1为空，或栈顶运算符为左括号“(”，则直接将此运算符入栈；
//      4.2 否则，若优先级比栈顶运算符的高，也将运算符压入s1；
//      4.3 否则，将s1栈顶的运算符弹出并压入到s2中，再次转到(4.1)与s1中新的栈顶运算符相比较；
// 5 遇到括号时：
//      5.1 如果是左括号“(”，则直接压入s1
//      5.2 如果是右括号“)”，则依次弹出s1栈顶的运算符，并压入s2，直到遇到左括号为止，此时将这一对括号丢弃
// 6 重复步骤2至5，直到表达式的最右边
// 7 将s1中剩余的运算符依次弹出并压入s2
// 8 依次弹出s2中的元素并输出，结果的逆序即为中缀表达式对应的后缀表达式
/**
 *
 * @param ls {array}
 */
function parseSuffixExpressionList (ls) {
    // 1 初始化两个栈：运算符栈s1和储存中间结果的栈s2
    let s1 = new Stack()
    let s2 = [] // 因为s2没有pop操作 且需要逆序 所以可以用Array替代

    // 2 从左至右扫描中缀表达式；
    for (const item of ls) {
        // 判断是不是一个 数字（正数、负数），包括整数、小数、0
        if (/^(-)?(([0-9])|([1-9]([0-9]+)))(.[0-9]+)?$/.test(item)) { // 3 遇到操作数时，将其压s2；
            s2.push(item)
        } else if (item === '(') {  // 5.1 如果是左括号“(”，则直接压入s1
            s1.push(item)
        } else if (item === ')') { // 5.2 如果是右括号“)”，则依次弹出s1栈顶的运算符，并压入s2，直到遇到左括号为止，此时将这一对括号丢弃
            while (!(s1.peek() === '(')) {
                s2.push(s1.pop())
            }
            s1.pop() // 将（ 弹出s1 消除小括号
        } else { // 4 遇到运算符时，比较其与s1栈顶运算符的优先级：
            // 4.3 否则，将s1栈顶的运算符弹出并压入到s2中，再次转到(4.1)与s1中新的栈顶运算符相比较；
            while (s1.size() !== 0 && OPERATION[s1.peek()] >= OPERATION[item]) {
                s2.push(s1.pop())
            }
            // 4.2 否则，若优先级比栈顶运算符的高，也将运算符压入s1；
            s1.push(item)
        }
    }
    // 7 将s1中剩余的运算符依次弹出并压入s2
    while (s1.size() !== 0) {
        s2.push(s1.pop())
    }
    return s2
}

/**
 *  将中缀表达式转成对应的list
 * @param s {string}
 */
function toInfixExpressionList (s) {
    s = s.replace(/\s/g, '')
    let ls = []
    let i = 0 // 用于遍历s 中缀表达式字符串
    let str = '' // 用于多位数拼接
    let c = '' // 每遍历一个字符就放到c
    while (i < s.length) {
        // 如果c是一个非数字 需要加入ls
        if (((c = s.charAt(i)).charCodeAt() < 48 || (c = s.charAt(i)).charCodeAt() > 57) && (c = s.charAt(i)).charCodeAt() !== 46) {
            ls.push(c)
            i++
        } else {
            str = ''
            while (i < s.length && ((c = s.charAt(i)).charCodeAt() >= 48 && (c = s.charAt(i)).charCodeAt() <= 57) || (c = s.charAt(i)).charCodeAt() === 46) {
                str += c
                i++
            }
            ls.push(str)
        }
    }
    return ls
}

/**
 *
 * @param ls {array}
 */
function calculate (ls) {
    let stack = new Stack()
    for (const item of ls) {
        if (/^(-)?(([0-9])|([1-9]([0-9]+)))(.[0-9]+)?$/.test(item)) {
            // 入栈
            stack.push(item)
        } else {
            // pop出两个数 运算 再入栈
            let num2 = Number.parseFloat(stack.pop())
            let num1 = Number.parseFloat(stack.pop())
            let res = 0
            if (item === '+') {
                res = accAdd(num1, num2)
            } else if (item === '-') {
                res = accSub(num1, num2)
            } else if (item === '*') {
                res = accMul(num1, num2)
            } else if (item === '/') {
                res = accDiv(num1, num2)
            } else {
                throw new Error(`Operator not supported`)
            }
            stack.push(res)
        }
    }
    return stack.pop()
}

//说明：javascript的加法结果会有误差，在两个浮点数相加的时候会比较明显。这个函数返回较为精确的加法结果。
//调用：accAdd(arg1,arg2)
//返回值：arg1加上arg2的精确结果
function accAdd (arg1, arg2) {
    let r1, r2, m
    try {r1 = arg1.toString().split('.')[1].length} catch (e) {r1 = 0}
    try {r2 = arg2.toString().split('.')[1].length} catch (e) {r2 = 0}
    m = Math.pow(10, Math.max(r1, r2))
    return (arg1 * m + arg2 * m) / m
}

//说明：javascript的减法结果会有误差，在两个浮点数相加的时候会比较明显。这个函数返回较为精确的减法结果。
//调用：accSub(arg1,arg2)
//返回值：arg1减上arg2的精确结果
function accSub (arg1, arg2) {
    return accAdd(arg1, -arg2)
}

//说明：javascript的乘法结果会有误差，在两个浮点数相乘的时候会比较明显。这个函数返回较为精确的乘法结果。
//调用：accMul(arg1,arg2)
//返回值：arg1乘以arg2的精确结果
function accMul (arg1, arg2) {
    let m = 0, s1 = arg1.toString(), s2 = arg2.toString()
    try {m += s1.split('.')[1].length} catch (e) {}
    try {m += s2.split('.')[1].length} catch (e) {}
    return Number(s1.replace('.', '')) * Number(s2.replace('.', '')) / Math.pow(10, m)
}

//说明：javascript的除法结果会有误差，在两个浮点数相除的时候会比较明显。这个函数返回较为精确的除法结果。
//调用：accDiv(arg1,arg2)
//返回值：arg1除以arg2的精确结果
function accDiv (arg1, arg2) {
    let t1 = 0, t2 = 0, r1, r2
    try {t1 = arg1.toString().split('.')[1].length} catch (e) {}
    try {t2 = arg2.toString().split('.')[1].length} catch (e) {}
    r1 = Number(arg1.toString().replace('.', ''))
    r2 = Number(arg2.toString().replace('.', ''))
    return (r1 / r2) * Math.pow(10, t2 - t1)
}

function test () {
    let expression = '12.8 + (2-3.55)*4+10/5.0'
    let result = doCalc(expression)
    console.log(result)
}

test()
